/*
* Copyright (c) 2006-2007 Erin Catto http:
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked, and must not be
* misrepresented the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* Converted for The Render Engine v2.0
* Aug. 4, 2010 Brett Fattori
*/

Engine.include("/physics/common/math/b2Math.js");

Engine.include("/physics/collision/b2BroadPhase.js");
Engine.include("/physics/collision/b2PairCallback.js");

Engine.include("/physics/dynamics/b2Body.js");
Engine.include("/physics/dynamics/b2BodyDef.js");
Engine.include("/physics/dynamics/b2CollisionFilter.js");
Engine.include("/physics/dynamics/b2ContactManager.js");
Engine.include("/physics/dynamics/b2TimeStep.js");
Engine.include("/physics/dynamics/b2Island.js");
Engine.include("/physics/dynamics/contacts/b2Contact.js");
Engine.include("/physics/dynamics/contacts/b2NullContact.js");


Engine.initObject("b2World", null, function() {

   var b2World = Base.extend({
   
      step: null,
      m_blockAllocator: null,
      m_stackAllocator: null,

      m_broadPhase: null,
      m_contactManager: null,

      m_bodyList: null,
      m_contactList: null,
      m_jointList: null,

      m_bodyCount: 0,
      m_contactCount: 0,
      m_jointCount: 0,

      // These bodies will be destroyed at the next time this.step.
      m_bodyDestroyList: null,

      m_gravity: null,
      m_allowSleep: null,

      m_groundBody: null,

      m_listener: null,
      m_filter: null,

      m_positionIterationCount: 0,
   
      constructor: function(worldAABB, gravity, doSleep) {
         // initialize instance variables for references
         this.step = new b2TimeStep();
         this.m_contactManager = new b2ContactManager();
         //


         this.m_listener = null;
         this.m_filter = b2CollisionFilter.b2_defaultFilter;

         this.m_bodyList = null;
         this.m_contactList = null;
         this.m_jointList = null;

         this.m_bodyCount = 0;
         this.m_contactCount = 0;
         this.m_jointCount = 0;

         this.m_bodyDestroyList = null;

         this.m_allowSleep = doSleep;

         this.m_gravity = gravity;

         this.m_contactManager.m_world = this;
         this.m_broadPhase = new b2BroadPhase(worldAABB, this.m_contactManager);

         var bd = new b2BodyDef();
         this.m_groundBody = this.CreateBody(bd);
      },
      
      // Set a callback to notify you when a joint is implicitly destroyed
      // when an attached body is destroyed.
      SetListener: function(listener) {
         this.m_listener = listener;
      },

      // Register a collision filter to provide specific control over collision.
      // Otherwise the default filter is used (b2CollisionFilter).
      SetFilter: function(filter) {
         this.m_filter = filter;
      },

      // Create and destroy rigid bodies. Destruction is deferred until the
      // the next call to this.Step. This is done so that bodies may be destroyed
      // while you iterate through the contact list.
      CreateBody: function(def) {
         //void* mem = this.m_blockAllocator.Allocate(sizeof(b2Body));
         var b = new b2Body(def, this);
         b.m_prev = null;

         b.m_next = this.m_bodyList;
         if (this.m_bodyList)
         {
            this.m_bodyList.m_prev = b;
         }
         this.m_bodyList = b;
         ++this.m_bodyCount;

         return b;
      },
      // Body destruction is deferred to make contact processing more robust.
      DestroyBody: function(b) {

         if (b.m_flags & b2Body.e_destroyFlag)
         {
            return;
         }

         // Remove from normal body list.
         if (b.m_prev)
         {
            b.m_prev.m_next = b.m_next;
         }

         if (b.m_next)
         {
            b.m_next.m_prev = b.m_prev;
         }

         if (b == this.m_bodyList)
         {
            this.m_bodyList = b.m_next;
         }

         b.m_flags |= b2Body.e_destroyFlag;
         //b2Settings.b2Assert(this.m_bodyCount > 0);
         --this.m_bodyCount;

         //b->~b2Body();
         //b.Destroy();
         // Add to the deferred destruction list.
         b.m_prev = null;
         b.m_next = this.m_bodyDestroyList;
         this.m_bodyDestroyList = b;
      },

      CleanBodyList: function() {
         this.m_contactManager.m_destroyImmediate = true;

         var b = this.m_bodyDestroyList;
         while (b)
         {
            //b2Settings.b2Assert((b.m_flags & b2Body.e_destroyFlag) != 0);

            // Preserve the next pointer.
            var b0 = b;
            b = b.m_next;

            // Delete the attached joints
            var jn = b0.m_jointList;
            while (jn)
            {
               var jn0 = jn;
               jn = jn.next;

               if (this.m_listener)
               {
                  this.m_listener.NotifyJointDestroyed(jn0.joint);
               }

               this.DestroyJoint(jn0.joint);
            }

            b0.Destroy();
            //this.m_blockAllocator.Free(b0, sizeof(b2Body));
         }

         // Reset the list.
         this.m_bodyDestroyList = null;

         this.m_contactManager.m_destroyImmediate = false;
      },

      CreateJoint: function(def) {
         var j = b2Joint.Create(def, this.m_blockAllocator);

         // Connect to the world list.
         j.m_prev = null;
         j.m_next = this.m_jointList;
         if (this.m_jointList)
         {
            this.m_jointList.m_prev = j;
         }
         this.m_jointList = j;
         ++this.m_jointCount;

         // Connect to the bodies
         j.m_node1.joint = j;
         j.m_node1.other = j.m_body2;
         j.m_node1.prev = null;
         j.m_node1.next = j.m_body1.m_jointList;
         if (j.m_body1.m_jointList) j.m_body1.m_jointList.prev = j.m_node1;
         j.m_body1.m_jointList = j.m_node1;

         j.m_node2.joint = j;
         j.m_node2.other = j.m_body1;
         j.m_node2.prev = null;
         j.m_node2.next = j.m_body2.m_jointList;
         if (j.m_body2.m_jointList) j.m_body2.m_jointList.prev = j.m_node2;
         j.m_body2.m_jointList = j.m_node2;

         // If the joint prevents collisions, then reset collision filtering.
         if (def.collideConnected == false)
         {
            // Reset the proxies on the body with the minimum number of shapes.
            var b = def.body1.m_shapeCount < def.body2.m_shapeCount ? def.body1 : def.body2;
            for (var s = b.m_shapeList; s; s = s.m_next)
            {
               s.ResetProxy(this.m_broadPhase);
            }
         }

         return j;
      },
      
      DestroyJoint: function(j) {

         var collideConnected = j.m_collideConnected;

         // Remove from the world.
         if (j.m_prev)
         {
            j.m_prev.m_next = j.m_next;
         }

         if (j.m_next)
         {
            j.m_next.m_prev = j.m_prev;
         }

         if (j == this.m_jointList)
         {
            this.m_jointList = j.m_next;
         }

         // Disconnect from island graph.
         var body1 = j.m_body1;
         var body2 = j.m_body2;

         // Wake up touching bodies.
         body1.WakeUp();
         body2.WakeUp();

         // Remove from body 1
         if (j.m_node1.prev)
         {
            j.m_node1.prev.next = j.m_node1.next;
         }

         if (j.m_node1.next)
         {
            j.m_node1.next.prev = j.m_node1.prev;
         }

         if (j.m_node1 == body1.m_jointList)
         {
            body1.m_jointList = j.m_node1.next;
         }

         j.m_node1.prev = null;
         j.m_node1.next = null;

         // Remove from body 2
         if (j.m_node2.prev)
         {
            j.m_node2.prev.next = j.m_node2.next;
         }

         if (j.m_node2.next)
         {
            j.m_node2.next.prev = j.m_node2.prev;
         }

         if (j.m_node2 == body2.m_jointList)
         {
            body2.m_jointList = j.m_node2.next;
         }

         j.m_node2.prev = null;
         j.m_node2.next = null;

         b2Joint.Destroy(j, this.m_blockAllocator);

         //b2Settings.b2Assert(this.m_jointCount > 0);
         --this.m_jointCount;

         // If the joint prevents collisions, then reset collision filtering.
         if (collideConnected == false)
         {
            // Reset the proxies on the body with the minimum number of shapes.
            var b = body1.m_shapeCount < body2.m_shapeCount ? body1 : body2;
            for (var s = b.m_shapeList; s; s = s.m_next)
            {
               s.ResetProxy(this.m_broadPhase);
            }
         }
      },

      // The world provides a single ground body with no collision shapes. You
      // can use this to simplify the creation of joints.
      GetGroundBody: function() {
         return this.m_groundBody;
      },

      // this.Step
      Step: function(dt, iterations) {

         var b;
         var other;

         this.step.dt = dt;
         this.step.iterations = iterations;
         if (dt > 0.0)
         {
            this.step.inv_dt = 1.0 / dt;
         }
         else
         {
            this.step.inv_dt = 0.0;
         }

         this.m_positionIterationCount = 0;

         // Handle deferred contact destruction.
         this.m_contactManager.CleanContactList();

         // Handle deferred body destruction.
         this.CleanBodyList();

         // Update contacts.
         this.m_contactManager.Collide();

         // Size the island for the worst case.
         var island = new b2Island(this.m_bodyCount, this.m_contactCount, this.m_jointCount, this.m_stackAllocator);

         // Clear all the island flags.
         for (b = this.m_bodyList; b != null; b = b.m_next)
         {
            b.m_flags &= ~b2Body.e_islandFlag;
         }
         for (var c = this.m_contactList; c != null; c = c.m_next)
         {
            c.m_flags &= ~b2Contact.e_islandFlag;
         }
         for (var j = this.m_jointList; j != null; j = j.m_next)
         {
            j.m_islandFlag = false;
         }

         // Build and simulate all awake islands.
         var stackSize = this.m_bodyCount;
         //var stack = (b2Body**)this.m_stackAllocator.Allocate(stackSize * sizeof(b2Body*));
         var stack = new Array(this.m_bodyCount);
         for (var k = 0; k < this.m_bodyCount; k++)
            stack[k] = null;

         for (var seed = this.m_bodyList; seed != null; seed = seed.m_next)
         {
            if (seed.m_flags & (b2Body.e_staticFlag | b2Body.e_islandFlag | b2Body.e_sleepFlag | b2Body.e_frozenFlag))
            {
               continue;
            }

            // Reset island and stack.
            island.Clear();
            var stackCount = 0;
            stack[stackCount++] = seed;
            seed.m_flags |= b2Body.e_islandFlag;;

            // Perform a depth first search (DFS) on the constraint graph.
            while (stackCount > 0)
            {
               // Grab the next body off the stack and add it to the island.
               b = stack[--stackCount];
               island.AddBody(b);

               // Make sure the body is awake.
               b.m_flags &= ~b2Body.e_sleepFlag;

               // To keep islands, we don't
               // propagate islands across static bodies.
               if (b.m_flags & b2Body.e_staticFlag)
               {
                  continue;
               }

               // Search all contacts connected to this body.
               for (var cn = b.m_contactList; cn != null; cn = cn.next)
               {
                  if (cn.contact.m_flags & b2Contact.e_islandFlag)
                  {
                     continue;
                  }

                  island.AddContact(cn.contact);
                  cn.contact.m_flags |= b2Contact.e_islandFlag;

                  other = cn.other;
                  if (other.m_flags & b2Body.e_islandFlag)
                  {
                     continue;
                  }

                  //b2Settings.b2Assert(stackCount < stackSize);
                  stack[stackCount++] = other;
                  other.m_flags |= b2Body.e_islandFlag;
               }

               // Search all joints connect to this body.
               for (var jn = b.m_jointList; jn != null; jn = jn.next)
               {
                  if (jn.joint.m_islandFlag == true)
                  {
                     continue;
                  }

                  island.AddJoint(jn.joint);
                  jn.joint.m_islandFlag = true;

                  other = jn.other;
                  if (other.m_flags & b2Body.e_islandFlag)
                  {
                     continue;
                  }

                  //b2Settings.b2Assert(stackCount < stackSize);
                  stack[stackCount++] = other;
                  other.m_flags |= b2Body.e_islandFlag;
               }
            }

            island.Solve(this.step, this.m_gravity);

            this.m_positionIterationCount = b2Math.b2Max(this.m_positionIterationCount, b2Island.m_positionIterationCount);

            if (this.m_allowSleep)
            {
               island.UpdateSleep(dt);
            }

            // Post solve cleanup.
            for (var i = 0; i < island.m_bodyCount; ++i)
            {
               // Allow static bodies to participate in other islands.
               b = island.m_bodies[i];
               if (b.m_flags & b2Body.e_staticFlag)
               {
                  b.m_flags &= ~b2Body.e_islandFlag;
               }

               // Handle newly frozen bodies.
               if (b.IsFrozen() && this.m_listener)
               {
                  var response = this.m_listener.NotifyBoundaryViolated(b);
                  if (response == b2WorldListener.b2_destroyBody)
                  {
                     this.DestroyBody(b);
                     b = null;
                     island.m_bodies[i] = null;
                  }
               }
            }
         }

         this.m_broadPhase.Commit();

         //this.m_stackAllocator.Free(stack);
      },

      // this.Query the world for all shapes that potentially overlap the
      // provided AABB. You provide a shape pointer buffer of specified
      // size. The number of shapes found is returned.
      Query: function(aabb, shapes, maxCount) {

         //void** results = (void**)this.m_stackAllocator.Allocate(maxCount * sizeof(void*));
         var results = new Array();
         var count = this.m_broadPhase.QueryAABB(aabb, results, maxCount);

         for (var i = 0; i < count; ++i)
         {
            shapes[i] = results[i];
         }

         //this.m_stackAllocator.Free(results);
         return count;
      },

      // You can use these to iterate over all the bodies, joints, and contacts.
      GetBodyList: function() {
         return this.m_bodyList;
      },
      
      GetJointList: function() {
         return this.m_jointList;
      },
      
      GetContactList: function() {
         return this.m_contactList;
      }
      
   }, {
      
      s_enablePositionCorrection: 1,
      s_enableWarmStarting: 1
      
   });
   
   return b2World;
   
});
